#include <thread>

#ifdef XG_LINUX
#include <termios.h>
#endif

#include <json/json.h>
#include <http/HttpServer.h>
#include <http/HttpResponse.h>

#define param_string(name) string name = request->getParameter(#name)
#define param_int(name) int name = stdx::atoi(request->getParameter(#name).c_str())

static string GetTemplateConfigString()
{
	u_char data[] = {
		0x2D,0x2D,0x20,0xE7,0x9B,0x91,0xE5,0x90,0xAC,0xE7,0xAB,0xAF,0xE5,0x8F,0xA3,0x0A,
		0x50,0x4F,0x52,0x54,0x20,0x3D,0x20,0x35,0x35,0x35,0x35,0x0A,0x0A,0x2D,0x2D,0x20,
		0xE4,0xB8,0xBB,0xE6,0x9C,0xBA,0xE5,0x9C,0xB0,0xE5,0x9D,0x80,0x0A,0x48,0x4F,0x53,
		0x54,0x20,0x3D,0x20,0x22,0x30,0x2E,0x30,0x2E,0x30,0x2E,0x30,0x22,0x0A,0x0A,0x2D,
		0x2D,0x20,0x53,0x53,0x4C,0xE7,0x9B,0x91,0xE5,0x90,0xAC,0xE7,0xAB,0xAF,0xE5,0x8F,
		0xA3,0x0A,0x53,0x53,0x4C,0x5F,0x50,0x4F,0x52,0x54,0x20,0x3D,0x20,0x37,0x37,0x37,
		0x37,0x0A,0x0A,0x2D,0x2D,0x20,0x50,0x45,0x4D,0xE8,0xAF,0x81,0xE4,0xB9,0xA6,0xE6,
		0x96,0x87,0xE4,0xBB,0xB6,0x0A,0x43,0x45,0x52,0x54,0x5F,0x46,0x49,0x4C,0x45,0x50,
		0x41,0x54,0x48,0x20,0x3D,0x20,0x22,0x63,0x65,0x72,0x74,0x2E,0x63,0x72,0x74,0x22,
		0x0A,0x0A,0x2D,0x2D,0x20,0x50,0x45,0x4D,0xE7,0xA7,0x81,0xE9,0x92,0xA5,0xE6,0x96,
		0x87,0xE4,0xBB,0xB6,0x0A,0x50,0x52,0x49,0x4B,0x45,0x59,0x5F,0x46,0x49,0x4C,0x45,
		0x50,0x41,0x54,0x48,0x20,0x3D,0x20,0x22,0x63,0x65,0x72,0x74,0x2E,0x6B,0x65,0x79,
		0x22,0x0A,0x0A,0x2D,0x2D,0x20,0xE6,0xB3,0xA8,0xE5,0x86,0x8C,0xE4,0xB8,0xAD,0xE5,
		0xBF,0x83,0xE7,0xAB,0xAF,0xE5,0x8F,0xA3,0x0A,0x52,0x4F,0x55,0x54,0x45,0x5F,0x50,
		0x4F,0x52,0x54,0x20,0x3D,0x20,0x38,0x38,0x38,0x38,0x0A,0x0A,0x2D,0x2D,0x20,0xE6,
		0xB3,0xA8,0xE5,0x86,0x8C,0xE4,0xB8,0xAD,0xE5,0xBF,0x83,0xE5,0x9C,0xB0,0xE5,0x9D,
		0x80,0x0A,0x52,0x4F,0x55,0x54,0x45,0x5F,0x48,0x4F,0x53,0x54,0x20,0x3D,0x20,0x22,
		0x31,0x32,0x37,0x2E,0x30,0x2E,0x30,0x2E,0x31,0x22,0x0A,0x0A,0x2D,0x2D,0x20,0xE8,
		0xB7,0xAF,0xE7,0x94,0xB1,0xE6,0x8E,0xA5,0xE5,0x8F,0xA3,0xE6,0x9D,0x83,0xE9,0x99,
		0x90,0x0A,0x52,0x4F,0x55,0x54,0x45,0x5F,0x41,0x43,0x43,0x45,0x53,0x53,0x20,0x3D,
		0x20,0x22,0x70,0x75,0x62,0x6C,0x69,0x63,0x22,0x0A,0x0A,0x2D,0x2D,0x20,0xE7,0x9B,
		0xAE,0xE7,0x9A,0x84,0xE5,0x9C,0xB0,0xE5,0x9D,0x80,0xE5,0x88,0x97,0xE8,0xA1,0xA8,
		0x28,0xE5,0x9C,0xB0,0xE5,0x9D,0x80,0x3A,0xE7,0xAB,0xAF,0xE5,0x8F,0xA3,0x3A,0xE6,
		0x9D,0x83,0xE9,0x87,0x8D,0x7C,0xE5,0x9C,0xB0,0xE5,0x9D,0x80,0x3A,0xE7,0xAB,0xAF,
		0xE5,0x8F,0xA3,0x3A,0xE6,0x9D,0x83,0xE9,0x87,0x8D,0x29,0x0A,0x44,0x45,0x53,0x54,
		0x5F,0x48,0x4F,0x53,0x54,0x5F,0x4C,0x49,0x53,0x54,0x20,0x3D,0x20,0x22,0x31,0x32,
		0x37,0x2E,0x30,0x2E,0x30,0x2E,0x31,0x3A,0x38,0x38,0x38,0x38,0x22,0x0A,0x0A,0x2D,
		0x2D,0x20,0xE5,0xA4,0x84,0xE7,0x90,0x86,0xE7,0xBA,0xBF,0xE7,0xA8,0x8B,0xE6,0x95,
		0xB0,0x0A,0x54,0x48,0x52,0x45,0x41,0x44,0x5F,0x53,0x49,0x5A,0x45,0x20,0x3D,0x20,
		0x33,0x32,0x0A,0x0A,0x2D,0x2D,0x20,0xE8,0xB7,0xA8,0xE5,0x9F,0x9F,0xE4,0xB8,0xBB,
		0xE6,0x9C,0xBA,0xE5,0x88,0x97,0xE8,0xA1,0xA8,0x28,0xE5,0x9C,0xB0,0xE5,0x9D,0x80,
		0x7C,0xE5,0x9C,0xB0,0xE5,0x9D,0x80,0x29,0x0A,0x41,0x4C,0x4C,0x4F,0x57,0x5F,0x4F,
		0x52,0x49,0x47,0x49,0x4E,0x20,0x3D,0x20,0x22,0x2A,0x22,0x0A,0x0A,0x2D,0x2D,0x20,
		0xE5,0x9C,0xB0,0xE5,0x9D,0x80,0xE7,0x99,0xBD,0xE5,0x90,0x8D,0xE5,0x8D,0x95,0x28,
		0xE5,0x9C,0xB0,0xE5,0x9D,0x80,0x7C,0xE5,0x9C,0xB0,0xE5,0x9D,0x80,0x29,0x0A,0x57,
		0x48,0x49,0x54,0x45,0x5F,0x48,0x4F,0x53,0x54,0x5F,0x4C,0x49,0x53,0x54,0x20,0x3D,
		0x20,0x22,0x22,0x0A,0x0A,0x2D,0x2D,0x20,0xE5,0x9C,0xB0,0xE5,0x9D,0x80,0xE9,0xBB,
		0x91,0xE5,0x90,0x8D,0xE5,0x8D,0x95,0x28,0xE5,0x9C,0xB0,0xE5,0x9D,0x80,0x7C,0xE5,
		0x9C,0xB0,0xE5,0x9D,0x80,0x29,0x0A,0x42,0x4C,0x41,0x43,0x4B,0x5F,0x48,0x4F,0x53,
		0x54,0x5F,0x4C,0x49,0x53,0x54,0x20,0x3D,0x20,0x22,0x22,0x0A,0x0A,0x2D,0x2D,0x20,
		0xE5,0x90,0x8C,0xE4,0xB8,0x80,0xE5,0x9C,0xB0,0xE5,0x9D,0x80,0xE6,0xAF,0x8F,0xE5,
		0x88,0x86,0xE9,0x92,0x9F,0xE8,0xAF,0xB7,0xE6,0xB1,0x82,0xE4,0xB8,0x8A,0xE9,0x99,
		0x90,0x0A,0x41,0x44,0x44,0x52,0x45,0x53,0x53,0x5F,0x52,0x45,0x51,0x55,0x45,0x53,
		0x54,0x5F,0x50,0x45,0x52,0x4D,0x49,0x4E,0x20,0x3D,0x20,0x31,0x30,0x30,0x30,0x0A,
		0x0A,0x2D,0x2D,0x20,0xE6,0x97,0xA5,0xE5,0xBF,0x97,0xE5,0xAD,0x98,0xE6,0x94,0xBE,
		0xE7,0x9B,0xAE,0xE5,0xBD,0x95,0x0A,0x4C,0x4F,0x47,0x46,0x49,0x4C,0x45,0x5F,0x50,
		0x41,0x54,0x48,0x20,0x3D,0x20,0x22,0x6C,0x6F,0x67,0x22,0x0A,0x0A,0x2D,0x2D,0x20,
		0xE6,0x97,0xA5,0xE5,0xBF,0x97,0xE7,0xBA,0xA7,0xE5,0x88,0xAB,0x28,0x30,0x2D,0x44,
		0x45,0x42,0x55,0x47,0x20,0x31,0x2D,0x54,0x49,0x50,0x53,0x20,0x32,0x2D,0x49,0x4E,
		0x46,0x4F,0x20,0x33,0x2D,0x49,0x4D,0x50,0x4F,0x52,0x54,0x41,0x4E,0x54,0x20,0x34,
		0x2D,0x45,0x52,0x52,0x4F,0x52,0x29,0x0A,0x4C,0x4F,0x47,0x46,0x49,0x4C,0x45,0x5F,
		0x4C,0x45,0x56,0x45,0x4C,0x20,0x3D,0x20,0x32,0x0A,0x0A,0x2D,0x2D,0x20,0xE5,0x8D,
		0x95,0xE4,0xB8,0xAA,0xE6,0x97,0xA5,0xE5,0xBF,0x97,0xE6,0x96,0x87,0xE4,0xBB,0xB6,
		0xE6,0x9C,0x80,0xE5,0xA4,0xA7,0xE5,0xA4,0xA7,0xE5,0xB0,0x8F,0x28,0x4B,0x42,0x29,
		0x0A,0x4C,0x4F,0x47,0x46,0x49,0x4C,0x45,0x5F,0x4D,0x41,0x58,0x53,0x49,0x5A,0x45,
		0x20,0x3D,0x20,0x31,0x30,0x30,0x30,0x30,0x00
	};

	return (char*)(data);
}

class RouteConfig
{
protected:
	int port;
	int access;
	string host;
	string version;
	HttpServer* app;
	mutable SpinMutex mtx;
	map<string, vector<HostItem>> hostmap;

public:
	static RouteConfig* Instance()
	{
		static RouteConfig cfg;
		return &cfg;
	}

public:
	RouteConfig()
	{
		port = 0;
		access = CGI_PUBLIC;
	}
	bool init()
	{
		app = HttpServer::Instance();

		string right;
		ConfigFile* cfg = app->getConfigFile();

		cfg->getVariable("ROUTE_HOST", host);
		cfg->getVariable("ROUTE_PORT", port);
		cfg->getVariable("ROUTE_ACCESS", right);

		stdx::tolower(right);
	
		if (right == "protect")
		{
			access = CGI_PROTECT;
		}
		else if (right == "private")
		{
			access = CGI_PRIVATE;
		}

		return port > 0 && host.length() > 0;
	}
	HostItem getHost() const
	{
		SpinLocker lk(mtx);

		return HostItem(host, port);
	}
	HostItem get(const string& path) const
	{
		HostItem item;
		SpinLocker lk(mtx);
		const auto it = hostmap.find(CgiMapData::GetKey(path));

		if (it == hostmap.end()) return item;

		const vector<HostItem>& vec = it->second;

		if (vec.empty()) return item;

		return vec[abs(rand()) % vec.size()];
	}
	vector<HostItem> getList(const string& path) const
	{
		SpinLocker lk(mtx);
		const auto it = hostmap.find(CgiMapData::GetKey(path));

		if (it == hostmap.end()) return vector<HostItem>();

		return it->second;
	}
	bool update(const string& host, int port)
	{
		CHECK_FALSE_RETURN(host.length() > 0 && port > 0);

		JsonElement json;
		HttpRequest request("exportroute");

		request.setParameter("access", access);
		request.setParameter("version", version);

		SmartBuffer buffer = request.getResult(host, port);

		CHECK_FALSE_RETURN(buffer.str() && json.init(buffer.str()));

		string version = json.asString("version");

		if (version == this->version)
		{
			SpinLocker lk(mtx);

			this->host = host;
			this->port = port;

			return true;
		}

		json = json.get("list");

		CHECK_FALSE_RETURN(json.isArray());

		map<string, vector<HostItem>> hostmap;

		for (JsonElement item : json)
		{
			int weight;
			HostItem host;
			JsonElement hostlist = item["list"];
			string path = item["path"].asString();
			vector<HostItem>& vec = hostmap[path];

			for (JsonElement data : hostlist)
			{
				weight = data["weight"].asInteger();
				host.host = data["host"].asString();
				host.port = data["port"].asInteger();

				if (weight < 1) weight = 1;
				if (weight > 9) weight = 9;

				for (int i = 0; i < weight; i++) vec.push_back(host);
			}
		}

		mtx.lock();

		std::swap(this->hostmap, hostmap);
		this->version = version;
		this->host = host;
		this->port = port;

		mtx.unlock();
		
		return true;
	}
};

class RouterItem : public HttpProcessBase
{
protected:
	HttpRequest* request;
	HttpResponse* response;

public:
	static HttpProcessBase* CreateCgi()
	{
		return new RouterItem();
	}
	static void DestroyCgi(HttpProcessBase* obj)
	{
		delete obj;
	}

	int getCountList(HttpServer* app)
	{
		JsonElement json;
		auto datmap = app->getTransCountMap();
		JsonElement arr = json.addArray("list");

		param_string(path);
		param_int(pagenum);
		param_int(pagesize);

		if (pagenum < 0 || pagesize < 1 || pagesize > 100)
		{
			pagenum = 0;
			pagesize = 100;
		}

		stdx::tolower(path);

		int daily = 0;
		int realtime = 0;
		vector<string> vec;
		
		for (auto& item : datmap)
		{
			if (path.length() > 0 && item.first.find(path) == string::npos) continue;

			realtime += item.second.realtime;
			daily += item.second.daily;
			vec.push_back(item.first);
		}

		int res = 0;
		int len = vec.size();

		std::sort(vec.begin(), vec.end());

		for (int i = 0; i < len; i++)
		{
			const auto& key = vec[i];
			const auto& item = datmap[key];

			if (res < pagesize && i >= pagenum * pagesize)
			{
				JsonElement data = arr[res++];

				data["realtime"] = item.realtime;
				data["meancost"] = item.meancost;
				data["mincost"] = item.mincost;
				data["maxcost"] = item.maxcost;
				data["daily"] = item.daily;
				data["path"] = key;
			}
		}

		json["pagecount"] = (int)(pagesize + vec.size() - 1) / pagesize;
		json["datetime"] = DateTime::ToString();
		json["realtime"] = realtime;
		json["daily"] = daily;
		json["code"] = res;

		out << json;

		return XG_OK;
	}
	bool updateRouteHost(RouteConfig* route)
	{
		if (route->getHost().canUse()) return false;

		param_int(routeport);
		param_string(routehost);

		if (routeport > 0)
		{
			if (routehost.empty() || routehost == LOCAL_IP) routehost = response->getSocket()->getAddress().host;

			if (route->update(routehost, routeport))
			{
				LogTrace(eINF, "update route list success");
			}
			else
			{
				LogTrace(eERR, "update route list failed");
			}
		}

		return true;
	}

	int doWork(HttpRequest* request, HttpResponse* response);
};

class HttpRouter : public HttpServer
{
public:
	static HttpRouter* Instance()
	{
		static HttpRouter* ptr = NULL;

		if (ptr) return ptr;

		Locker lk(Process::Instance()->getMutext());
		string name = stdx::format("GPTR_%s_%s_%s", "HttpServer.cpp", "HttpServer", __FUNCTION__);

		if (ptr = (HttpRouter*)(Process::GetObject(name))) return ptr;

		Process::SetObject(name, ptr = new HttpRouter());

		return ptr;
	}
	bool init(const string& filepath)
	{
		CHECK_FALSE_RETURN(HttpServer::init(filepath));

		RouteConfig::Instance()->init();

		return true;
	}

	CgiMapData getCgiMapData(const string& url);
};

class HttpApplication : public Application
{
	friend class RouterItem;
	friend class HttpRouter;

protected:
	bool locale;
	Sharemem shm;
	ConfigFile cfg;
	HttpRouter* svr;
	vector<HostItem> hostvec;

	void printTips() const
	{
		puts(" webrouter command");
		puts("-----------------------------------------");
		puts("  -l   : log monitor");
		puts("  -k   : stop webrouter");
		puts("  -r   : reload webrouter");
		puts("  -s   : restart webrouter");
		puts("  -etc : export template configure");
		puts("-----------------------------------------");
		puts("");
	}
	bool checkHost(const char* client)
	{
		static SpinMutex mtx;
		time_t now = time(NULL);
		static time_t utime = 0;
		static int connpermin = 0;
		static set<int> whitehostset;
		static set<int> blackhostset;
		int key = GetHostInteger(client);

		auto loadHostList = [&](){
			string str;
			static time_t utime = 0;
			static set<string> whitefileset;
			static set<string> blackfileset;
			static vector<string> whitehostlist;
			static vector<string> blackhostlist;
			static map<string, set<int>> whitehostmap;
			static map<string, set<int>> blackhostmap;

			if (path::mtime(cfg.getFilePath()) < utime)
			{
				whitehostset.clear();
				blackhostset.clear();
			}
			else
			{
				CHECK_FALSE_RETURN(cfg.reload() && loadConfig(NULL));

				locale = false;
				connpermin = 0;
				whitehostset.clear();
				blackhostset.clear();
				whitefileset.clear();
				blackfileset.clear();
				whitehostmap.clear();
				blackhostmap.clear();
				whitehostlist.clear();
				blackhostlist.clear();

				cfg.getVariable("ADDRESS_REQUEST_PERMIN", connpermin);

				if (cfg.getVariable("WHITE_HOST_LIST", str)) stdx::split(whitehostlist, str, "|");
				if (cfg.getVariable("BLACK_HOST_LIST", str)) stdx::split(blackhostlist, str, "|");

				map<string, string> datamap;

				if (cfg.getMapData(datamap) > 0)
				{
					for (auto& item : datamap)
					{
						if (item.first.length() > 6 && item.first[3] == '@')
						{
							locale = true;

							break;
						}
					}
				}
			}

			for (string& item : whitehostlist)
			{
				item = stdx::trim(item);

				if (Socket::IsHostString(item))
				{
					whitehostset.insert(GetHostInteger(item.c_str()));
				}
				else
				{
					auto it = whitefileset.find(item);
					bool up = it == whitefileset.end() || path::mtime(item) >= utime;

					if (up && stdx::GetFileContent(str, item) >= 0)
					{
						vector<string> vec;
						set<int>& hostset = whitehostmap[item];

						hostset.clear();

						stdx::split(vec, stdx::replace(str, "\n", "|"), "|");

						for (string& item : vec)
						{
							item = stdx::trim(item);

							if (Socket::IsHostString(item))
							{
								hostset.insert(GetHostInteger(item.c_str()));
							}
						}

						LogTrace(eIMP, "load host config[%s] success", item.c_str());

						whitefileset.insert(item);
					}
				}
			}

			for (auto& item : whitehostmap)
			{
				for (int host : item.second) whitehostset.insert(host);
			}

			for (string& item : blackhostlist)
			{
				item = stdx::trim(item);

				if (Socket::IsHostString(item))
				{
					blackhostset.insert(GetHostInteger(item.c_str()));
				}
				else
				{
					auto it = blackfileset.find(item);
					bool up = it == blackfileset.end() || path::mtime(item) >= utime;

					if (up && stdx::GetFileContent(str, item) >= 0)
					{
						vector<string> vec;
						set<int>& hostset = blackhostmap[item];

						hostset.clear();

						stdx::split(vec, stdx::replace(str, "\n", "|"), "|");

						for (string& item : vec)
						{
							item = stdx::trim(item);

							if (Socket::IsHostString(item))
							{
								hostset.insert(GetHostInteger(item.c_str()));
							}
						}

						LogTrace(eIMP, "load host config[%s] success", item.c_str());

						blackfileset.insert(item);
					}
				}
			}

			for (auto& item : blackhostmap)
			{
				for (int host : item.second) blackhostset.insert(host);
			}

			utime = now;

			return true;
		};

		mtx.lock();

		if (utime + 5 < now)
		{
			loadHostList();
			utime = now;
		}

		if (whitehostset.size() > 0 && whitehostset.find(key) == whitehostset.end())
		{
			mtx.unlock();

			LogTrace(eERR, "client[%s] unbelievable", client);
			
			return false;
		}

		if (blackhostset.size() > 0 && blackhostset.find(key) != blackhostset.end())
		{
			mtx.unlock();

			LogTrace(eERR, "client[%s] unbelievable", client);
			
			return false;
		}

		if (connpermin > 0)
		{
			static unordered_map<long long, int> countmap;

			now /= 60;

			if (countmap.size() > 100000)
			{
				auto it = countmap.begin();

				while (it != countmap.end())
				{
					if (now == it->first >> 32)
					{
						++it;
					}
					else
					{
						countmap.erase(it++);
					}
				}
			}

			long long tmp = key;
			auto it = countmap.find(tmp += now << 32);

			if (it == countmap.end())
			{
				countmap[tmp] = 0;
			}
			else
			{
				int& num = it->second;

				if (num > connpermin)
				{
					mtx.unlock();

					LogTrace(eERR, "client[%s] request frequently", client);
					
					return false;
				}

				++num;
			}
		}

		mtx.unlock();

		return true;
	}
	bool loadConfig(const char* filepath)
	{
		string str;

		if (filepath) CHECK_FALSE_RETURN(cfg.init(filepath));

		hostvec.clear();

		if (cfg.getVariable("DEST_HOST_LIST", str))
		{
			vector<string> vec;

			stdx::split(vec, str, "|");

			for (auto& item : vec)
			{
				vector<string> tmp;

				item = stdx::trim(item);
				stdx::split(tmp, item, ":");

				if (tmp.size() > 0 || tmp[0].length() > 0)
				{
					int weight = 1;
					HostItem host(tmp[0], 0);

					if (tmp.size() > 1) host.port = stdx::atoi(tmp[1].c_str());
					if (tmp.size() > 2) weight = stdx::atoi(tmp[2].c_str());

					if (host.port > 0 || weight > 0)
					{
						if (weight > 1000) weight = 1000;

						for (int i = 0; i < weight; i++)
						{
							hostvec.push_back(host);
						}
					}
				}
			}
		}

		return true;
	}

public:
	HttpApplication() : svr(HttpRouter::Instance()), locale(false){}

	void clean()
	{
		LogThread::Instance()->wait();

		svr->initSystem(true);

		shm.close();
	}
	bool commit(const char* host, int port)
	{
		Socket sock;
		char cmd = 0;

		CHECK_FALSE_RETURN(sock.init());
		CHECK_FALSE_RETURN(sock.connect(host, port));
		CHECK_FALSE_RETURN(sock.writeObject(cmd));

		sock.close();

		return true;
	}
	bool main()
	{
		const char* path = NULL;

		if (GetCmdParamCount() <= 1 || GetCmdParam("?") || GetCmdParam("--help"))
		{
			printTips();

			return true;
		}

		if ((path = GetCmdParam("-etc")))
		{
			if (*path)
			{
				if (GetCmdParam("-i") && path::size(path) > 0)
				{
					CHECK_FALSE_RETURN(cmdx::CheckCommand("file[%s] exists, overwrite or not ? (y/n)", path::name(path).c_str()));
				}

				TextFile log;

				if (log.open(path, true))
				{
					log.puts(GetTemplateConfigString());

					puts("export template config success");
				}
				else
				{
					puts("export template config failed");
				}
			}
			else
			{
				puts(GetTemplateConfigString().c_str());
			}

			return true;
		}

		path = GetCmdParam(1);

		if (path == NULL || path::type(path) < eFILE)
		{
			HttpRouter::ShareData* shd = HttpRouter::Instance()->getShareData();

			if (shd)
			{
				char* cmd = shd->data;

				if (*cmd == 'r')
				{
					puts("reloading webrouter ...");

					return false;
				}

				int port = shd->port;
				const char* host = shd->host;

				if (strcmp(host, HOST_IP) == 0) host = LOCAL_IP;

				if (GetCmdParam("-restart") == NULL && commit(host, port))
				{
					auto command = [&](char flag){
						int num = 100;

						cmd[0] = flag;
						cmd[1] = '-';

						commit(host, port);

						while (num-- > 0 && cmd[1] == '-') Sleep(100);
					};

					if (GetCmdParam("-k"))
					{
						command('k');

						if (cmd[1] == 0)
						{
							puts("stop webrouter success");
						}
						else
						{
							puts("stop webrouter failed");
						}
					}
					else if (GetCmdParam("-s"))
					{
						command('s');

						if (cmd[1] == 0)
						{
							puts("restart webrouter success");
						}
						else
						{
							puts("restart webrouter failed");
						}
					}
					else if (GetCmdParam("-r"))
					{
						command('r');

						if (cmd[1] == 0)
						{
							puts("reload webrouter success");
						}
						else if (cmd[1] == 1)
						{
							puts("reload webrouter failed");
						}
						else
						{
							puts("reloading webrouter ...");
						}
					}
					else if (GetCmdParam("-l"))
					{
						MemQueue mq;
						Semaphore sem;

						if (sem.open(HttpRouter::GetSemaphoreName()))
						{
							if (sem.wait())
							{
								mq.open(shd->logdata);

								sem.release();
								
								std::thread([&](){
									while (true)
									{
										int num = 3;

										while (--num >= 0)
										{
											sleep(3);

											if (Socket().connect(host, port)) break;
										}

										if (num < 0) ErrorExit(0);
									}
								}).detach();
								
								command('l');
								
								while (true)
								{
									if (sem.wait())
									{
										int hdrsz = 32;
										SmartBuffer data = mq.pop();

										sem.release();

										if (data.isNull())
										{
											Sleep(10);

											continue;
										}

										string msg = stdx::syscode(data.str());
										string head(data.str(), data.str() + stdx::minval(data.size(), hdrsz));

										if (head.find("|ERR|") != string::npos)
										{
											SetConsoleTextColor(eRED);

											fwrite(msg.c_str(), msg.length(), 1, stdout);

											SetConsoleTextColor(eWHITE);
										}
										else if (head.find("|IMP|") != string::npos)
										{
											SetConsoleTextColor(eYELLOW);

											fwrite(msg.c_str(), msg.length(), 1, stdout);

											SetConsoleTextColor(eWHITE);
										}
										else
										{
											fwrite(msg.c_str(), msg.length(), 1, stdout);
										}

										fflush(stdout);
									}
								}
							}
						}
					}
					else
					{
						puts("undefined command");

						return false;
					}
					
					return true;
				}
			}
		}

		if (path == NULL || *path == 0)
		{
			puts("please input configure filename");
	
			return false;
		}

		if (GetCmdParam("-l")) LogThread::Instance()->setLogFlag(2);
	
		if (loadConfig(path) && HttpRouter::Instance()->init(path))
		{
			std::thread([=](){
				RouteConfig* route = RouteConfig::Instance();

				while (true)
				{
					HostItem item = route->getHost();

					if (item.canUse())
					{
						if (route->update(item.host, item.port))
						{
							LogTrace(eINF, "update route list success");
						}
						else
						{
							LogTrace(eERR, "update route list failed");
						}
					}

					sleep(5);
				}
			}).detach();

			Process::SetCommonExitSignal();

			LogTrace(eINF, "enter process loop ...");
#ifdef XG_LINUX
			std::thread([](){
				struct termios tm;

				while (tcgetattr(STDIN_FILENO, &tm) >= 0) sleep(1);

				close(STDIN_FILENO);
				close(STDOUT_FILENO);
				close(STDERR_FILENO);
			}).detach();
#endif
			HttpRouter::Instance()->loop();
		}
		else
		{
			LogTrace(eERR, "initialize server failed");
		}
		
		LogTrace(eERR, "process exiting ...");
	
		clean();
		
		return false;
	}
};

CgiMapData HttpRouter::getCgiMapData(const string& url)
{
	string key = CgiMapData::GetKey(url);
	static HttpApplication* app = (HttpApplication*)(Process::GetApplication());

	if (app->locale)
	{
		CgiMapData item = HttpServer::getCgiMapData(key);

		if (item.url.length() > 0) return item;
	}

	CgiMapData item(CgiMapData::CGI_FLAG, CgiMapData::NONE_CODE, url);

	if (key == "recvfile") item.maxsz = max(HTTP_REQDATA_MAXSIZE, XG_MEMFILE_MAXSZ);

	item.destroy_cgi = RouterItem::DestroyCgi;
	item.create_cgi = RouterItem::CreateCgi;

	return item;
}

int RouterItem::doWork(HttpRequest* request, HttpResponse* response)
{
	string host = response->getSocketHost();
	static RouteConfig* route = RouteConfig::Instance();
	static HttpApplication* app = (HttpApplication*)(Process::GetApplication());

	if (!app->checkHost(host.c_str())) return XG_AUTHFAIL;

	this->request = request;
	this->response = response;

	string path = CgiMapData::GetKey(request->getPath());

	if (path == "getcgilist")
	{
		updateRouteHost(route);
	}
	else if (path == "getcountlist")
	{
		HostItem item = route->getHost();

		if (host == item.host) return getCountList(app->svr);
	}

	HostItem item = route->get(path);

	if (item.host.empty())
	{
		if (app->hostvec.empty()) return XG_NOTFOUND;

		item = app->hostvec[abs(rand()) % app->hostvec.size()];
	}

	sp<Socket> sock = SocketPool::Connect(item.host, item.port);

	if (!sock) return XG_SENDFAIL;

	request->setHeadValue("X-Forwarded-For", host);

	sp<HttpResponse> result = request->getResponse(sock, false);

    if (!result) return XG_RECVFAIL;

	response->setErrorCode(result->getErrorCode());
	response->setErrorString(result->getErrorString());

	const HttpHeadNode& node = result->node;
	const vector<string>& keys = node.getKeys();

	for (const string& key : keys)
	{
		string tag = stdx::tolower(key);

		if (tag == "connection" || tag == "keep-alive" || tag == "content-length") continue;

		response->setHeadValue(key, node.getValue(key));
	}

	SmartBuffer data = result->getResult();
	int len = data.size();

	if (len <= 0) return XG_FAIL;

	if (createFile(len))
	{
		file->write(data.str(), len);

		return data.size();
	}

	return XG_SYSERR;
}

START_APP(HttpApplication)